import re
import sys

from argparse import ArgumentParser, RawDescriptionHelpFormatter

from errors import CommandError
import log

class Command(object):
	commands = dict()
	stdout = sys.stdout

	@staticmethod
	def register(command_name):
		def wrapper(command_class):
			assert issubclass(command_class, Command)
			assert command_name not in Command.commands

			Command.commands[command_name] = command_class
			command_class.command_name = command_name

			return command_class
		return wrapper

	def add_arguments(cls, parser):
		pass

	def execute(self, args):
		raise NotImplementedError()


def main(args):
	parser = ArgumentParser(add_help=False)

	if not args:
		parser.print_help()
		parser.exit()

	command_name = args[0]; args = args[1:]

	def make_prog(command_name):
		return '{} {}'.format(sys.argv[0], command_name)

	def make_parser(command):
		parser = ArgumentParser(
			add_help=False,
			description=str(getattr(command, '__doc__', '')),
			prog=make_prog(command.command_name)
			)
		command.add_arguments(parser)
		return parser

	if command_name == 'help':
		parser.prog = make_prog('help')
		parser.add_argument('command', choices=Command.commands, nargs='?', help='name of the command to show the help on')
		args = parser.parse_args(args)

		if args.command:
			make_parser(Command.commands[args.command]()).print_help()
		else:
			parser.formatter_class = RawDescriptionHelpFormatter

			re_title = re.compile('^(.+)$', re.MULTILINE)
			def get_title(command):
				m = re_title.match(str(getattr(command, '__doc__', '')))
				return m.group(0) if m else ''

			parser.epilog = 'available commands:\n' + '\n'.join(
				'  {} {}'.format(
					n.ljust(16), get_title(c)) for n,c in Command.commands.iteritems()
			)
			parser.print_help()

	elif command_name in Command.commands:
		command = Command.commands[command_name]()
		args = make_parser(command).parse_args(args)
		try:
			command.execute(args)
		except CommandError as error:
			sys.exit(msg=str(error), status=1)
		sys.exit()	
	else:
		parser.error('unknown command: {0}'.format(command_name))
